操作系统过去是允许可执行栈的，但现在情况已经改变：在 Ubuntu 操作系统中，程序（和共享库）的二进制映像必须声明它们是否需要可执行栈，即它们需要在程序头中设置一个标记字段。内核或动态链接器使用此标记来决定是否让运行的程序的栈可执行或不可执行。此标记由 \texttt{gcc} 自动完成，默认情况下使栈不可执行。我们可以在编译中使用 \texttt{"-z noexecstack"} 标志专门使其不可执行。
在我们之前的任务中，我们使用 \texttt{"-z execstack"} 使栈可执行。

在此任务中，我们将使栈不可执行。我们将在 \texttt{shellcode} 文件夹中进行这个实验。
\texttt{call\_shellcode} 程序会在堆栈上放置 shellcode 的副本，然后从栈上执行代码。
请重新编译 \texttt{call\_shellcode.c} 生成 \texttt{a32.out} 和 \texttt{a64.out}，但不使用 \texttt{"-z execstack"} 选项。运行它们，并描述和解释你的观察。

\paragraph{击败非执行栈防御措施。}
需要注意的是，不可执行栈仅使得在栈上运行 shellcode 不可能，但这并不能完全防止缓冲区溢出攻击，因为还有其他方式来运行恶意代码。例如 {\em return-to-libc} 攻击。我们为此设计了一个单独的实验。如果你感兴趣，请参见我们的 Return-to-Libc 攻击实验。